﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Moon.Sql
{
    public abstract class InsertableProvide<T> : IInsertable<T>
    {
        protected IDatabase Database;
        protected IExpression ExpressionProvideObj;
        protected T SaveObject;
        protected List<T> SaveList;
        protected MapperTable MasterTable;
        protected string MasterTableName;
        protected string _IgnoreColumns;
        protected bool IsQuote = true;
        protected int batchSize = 1000;
        protected int batchColumnSize = 2100;

        public InsertableProvide(IDatabase _Database)
        {
            Database = _Database;

            MasterTable = ClassMapper.Mapping<T>();
            MasterTableName = MasterTable.TableName;
            ExpressionProvideObj = new ExpressionProvide(Database.SqlDialect, MasterTable);
        }

        public IInsertable<T> Insertable(T t)
        {
            SaveObject = t;
            return this;
        }

        public IInsertable<T> Insertable(List<T> list)
        {
            SaveList = list;
            return this;
        }

        public IInsertable<T> TableName(string tableName = null, bool isQuote = true)
        {
            IsQuote = isQuote;
            if (!string.IsNullOrWhiteSpace(tableName))
            {
                MasterTableName = tableName;
            }
            return this;
        }

        public IInsertable<T> IgnoreColumns(Expression<Func<T, object>> columns)
        {
            _IgnoreColumns = ExpressionProvideObj.ExpressionRouter(columns, isQuote: false).Sql;
            return this;
        }

        public IInsertable<T> IgnoreColumns(string[] columns)
        {
            if (columns?.Length > 0)
            {
                _IgnoreColumns = string.Join(",", columns);
            }
            return this;
        }

        public IInsertable<T> SetColumns(Expression<Func<T, object>> columns)
        {
            string columnSql = ExpressionProvideObj.ExpressionRouter(columns, isQuote: false).Sql;
            string[] colArray = columnSql.Split(',');
            _IgnoreColumns = string.Join(",", MasterTable.Properties.Where(i => !colArray.Contains(i.Name)).Select(i => i.Name));
            return this;
        }

        public IInsertable<T> SetColumns(string[] columns)
        {
            if (columns?.Length > 0)
            {
                _IgnoreColumns = string.Join(",", MasterTable.Properties.Where(i => !columns.Contains(i.Name)).Select(i => i.Name));
            }
            return this;
        }

        public virtual int BulkInsert()
        {
            if (SaveList == null || SaveList.Count == 0)
            {
                return 0;
            }
            return Database.BulkInsert(ToDataTable(), MasterTableName);
        }

        public virtual async Task<int> BulkInsertAsync()
        {
            if (SaveList == null || SaveList.Count == 0)
            {
                return 0;
            }
            return await Database.BulkInsertAsync(ToDataTable(), MasterTableName);
        }

        public virtual int Execute()
        {
            SqlBuilderResult result = ToSql();
            return Database.Execute(result.Sql, result.DynamicParameters);
        }

        public virtual async Task<int> ExecuteAsync()
        {
            SqlBuilderResult result = ToSql();
            return await Database.ExecuteAsync(result.Sql, result.DynamicParameters);
        }

        protected virtual SqlBuilderResult ToSqlBatch()
        {
            var columns = MasterTable.Properties.Where(i => !i.IsIdentity && !i.Ignored);
            if (!columns.Any())
            {
                throw new ArgumentException("no column");
            }
            //过滤忽略的列
            if (!string.IsNullOrWhiteSpace(_IgnoreColumns))
            {
                var ignoreColumns = _IgnoreColumns.Split(',').Distinct().ToList();
                columns = columns.Where(i => !ignoreColumns.Contains(i.Name));
            }
            var columnNames = columns.Select(i => Database.SqlDialect.SetSqlName(i.ColumnName));
            string columnSql = string.Join(",", columnNames);
            StringBuilder sbSql = new StringBuilder();
            sbSql.Append("insert into ").Append(IsQuote ? Database.SqlDialect.SetSqlName(MasterTableName) : MasterTableName).Append(" (");
            sbSql.Append(columnSql).Append(")");
            sbSql.AppendLine(" values");

            DynamicParameters parameters = new DynamicParameters();
            int colIndex = 0;
            foreach (var item in SaveList)
            {
                sbSql.Append("(");
                foreach (var itemCol in columns.ToList())
                {
                    var property = itemCol.PropertyInfo;
                    object value = property.GetValue(item);
                    string paramName = Database.SqlDialect.ParameterPrefix + property.Name + colIndex;
                    sbSql.Append($"{paramName},");
                    parameters.Add(paramName, value);
                    ++colIndex;
                }
                sbSql.Remove(sbSql.Length - 1, 1);
                sbSql.AppendLine("),");
            }
            return new SqlBuilderResult()
            {
                Sql = sbSql.ToString().TrimEnd(",\r\n".ToArray()),
                DynamicParameters = parameters
            };
        }

        public abstract long ExecuteIdentity();
        public abstract Task<long> ExecuteIdentityAsync();
        public abstract DataTable ToDataTable();

        public virtual SqlBuilderResult ToSql()
        {
            if (SaveObject == null && SaveList == null)
            {
                throw new Exception("save object is empty");
            }
            if (SaveList == null) SaveList = new List<T>();
            if (SaveObject != null) SaveList.Add(SaveObject);
            CheckData();
            return ToSqlBatch();
        }

        protected virtual void CheckData()
        {
            if (SaveList.Count > batchSize)
                throw new Exception($"The incoming request has too many data. Supports a maximum of {batchSize}");
            var columns = MasterTable.Properties.Where(i => !i.IsIdentity && !i.Ignored);
            if((columns.Count() * SaveList.Count) > batchColumnSize)
                throw new Exception($"The incoming request has too many parameters. Supports a maximum of {batchColumnSize}");
        }
    }
}
